package uk.co.deanwild.materialshowcaseview;

import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.LifecycleObserver;
import ohos.agp.components.*;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.*;
import ohos.agp.window.service.Display;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;
import uk.co.deanwild.materialshowcaseview.shape.*;
import uk.co.deanwild.materialshowcaseview.target.Target;
import uk.co.deanwild.materialshowcaseview.target.ViewTarget;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * 高亮引导页
 */
public class MaterialShowcaseView extends StackLayout implements Component.TouchEventListener,
        Component.ClickedListener,
        Component.BindStateChangedListener {
    public static final int DEFAULT_SHAPE_PADDING = 10;
    public static final int DEFAULT_TOOLTIP_MARGIN = 10;
    long DEFAULT_DELAY = 0;
    long DEFAULT_FADE_TIME = 300;

    private Target mTarget;
    private Shape mShape;
    private int mXPosition;
    private int mYPosition;
    private boolean mWasDismissed = false, mWasSkipped = false;
    private int mShapePadding = DEFAULT_SHAPE_PADDING;
    private int tooltipMargin = DEFAULT_TOOLTIP_MARGIN;

    private Text mTitleTextView;
    private Text mContentTextView;
    private Text mDismissButton;
    private boolean mHasCustomGravity;
    private Text mSkipButton;
    private int mGravity;
    private int mContentBottomMargin;
    private int mContentTopMargin;
    private boolean mDismissOnTouch = false;
    private boolean mRenderOverNav = false;
    private int mMaskColour;
    private IAnimationFactory mAnimationFactory;
    private static final boolean mShouldAnimate = true;
    private boolean mUseFadeAnimation = true;
    private long mFadeDurationInMillis = DEFAULT_FADE_TIME;
    private EventHandler mHandler;
    private long mDelayInMillis = DEFAULT_DELAY;
    private boolean mSingleUse = false;
    List<IShowcaseListener> mListeners;
    private IDetachedListener mDetachedListener;
    private boolean mTargetTouchable = false;
    private boolean mDismissOnTargetTouch = true;

    private ShowcaseTooltip toolTip;
    private boolean toolTipShown;

    private Component contentHintView;

    public MaterialShowcaseView(Context context) {
        this(context, null);
    }

    public MaterialShowcaseView(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    public MaterialShowcaseView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init();
        setTouchEventListener(this);
        setBindStateChangedListener(this);
        setClickedListener(component -> {
            //防止点击事件下透到底下布局
        });
    }


    private void init() {
        mListeners = new ArrayList<>();

        mMaskColour = Color.getIntColor(ShowcaseConfig.DEFAULT_MASK_COLOUR);
        setVisibility(INVISIBLE);

        contentHintView = LayoutScatter.getInstance(getContext()).parse(ResourceTable.Layout_showcase_content, null, true);
        mTitleTextView = (Text) contentHintView.findComponentById(ResourceTable.Id_text_title);
        mContentTextView = (Text) contentHintView.findComponentById(ResourceTable.Id_text_content);
        mDismissButton = (Text) contentHintView.findComponentById(ResourceTable.Id_text_dismiss);
        mSkipButton = (Text) contentHintView.findComponentById(ResourceTable.Id_text_skip);

        mDismissButton.setClickedListener(this);
        mSkipButton.setClickedListener(this);
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        if (mDismissOnTouch) {
            hide();
        }
        MmiPoint pointerPosition = touchEvent.getPointerPosition(touchEvent.getIndex());
        int x = (int) pointerPosition.getX();
        int y = (int) pointerPosition.getY();
        if (mTargetTouchable && mTarget.getBounds().contains(x, y, x, y)) {
            if (mDismissOnTargetTouch) {
                hide();
            }
            return false;
        }
        return true;
    }

    @Override
    public void onComponentBoundToWindow(Component component) {
    }

    @Override
    public void onComponentUnboundFromWindow(Component component) {
        if (!mWasDismissed && mSingleUse) {
            PrefsManager.getInstance().resetShowcase();
        }
        notifyOnDismissed();
    }

    private void notifyOnDisplayed() {
        if (mListeners != null) {
            for (IShowcaseListener listener : mListeners) {
                listener.onShowcaseDisplayed(this);
            }
        }
    }

    private void notifyOnDismissed() {
        if (mListeners != null) {
            for (IShowcaseListener listener : mListeners) {
                listener.onShowcaseDismissed(this);
            }

            mListeners.clear();
            mListeners = null;
        }

        if (mDetachedListener != null) {
            mDetachedListener.onShowcaseDetached(this, mWasDismissed, mWasSkipped);
        }
    }

    @Override
    public void onClick(Component component) {
        if (component.getId() == ResourceTable.Id_text_dismiss) {
            hide();
        } else if (component.getId() == ResourceTable.Id_text_skip) {
            skip();
        }
    }

    public void setAbility(Ability ability) {
        ability.getLifecycle().addObserver(new LifecycleObserver() {
            @Override
            public void onStop() {
                super.onStop();

                if (!mWasDismissed && mSingleUse) {
                    PrefsManager.getInstance().resetShowcase();
                }

                notifyOnDismissed();
            }
        });
    }

    public void setGravity(int gravity) {
        mHasCustomGravity = LayoutAlignment.UNSET != gravity;
        mGravity = gravity;
        mContentTopMargin = mContentBottomMargin = 0;
    }

    public void setTarget(Target target) {
        mTarget = target;

        updateDismissButton();

        if (mTarget != null) {
            if (!mRenderOverNav) {
                ComponentContainer.LayoutConfig contentLP = this.getLayoutConfig();
                int mBottomMargin = 0;
                if (contentLP != null && contentLP.getMarginBottom() != mBottomMargin)
                    contentLP.setMarginBottom(mBottomMargin);
            }

            ViewTarget viewTarget = (ViewTarget) mTarget;
            viewTarget.getView().addDrawTask(new DrawTask() {
                @Override
                public void onDraw(Component component, Canvas canvas) {
                    targetShape();
                }
            });
        }
    }

    private void targetShape(){
        Point targetPoint = mTarget.getPoint();
        Rect targetBounds = mTarget.getBounds();
        setPosition(targetPoint);

        Optional<Display> defaultDisplay = DisplayManager.getInstance().getDefaultDisplay(mContext);
        int height = 0;
        if(defaultDisplay.isPresent()){
            height = defaultDisplay.get().getAttributes().height;
        }
        int midPoint = height / 2;
        int yPos = targetPoint.getPointYToInt();

        int radius = Math.max(targetBounds.getHeight(), targetBounds.getWidth()) / 2;
        if (mShape != null) {
            mShape.updateTarget(mTarget);
            radius = mShape.getHeight() / 2;
        }

        if (!mHasCustomGravity) {
            if (yPos > midPoint) {
                mContentTopMargin = 0;
                mContentBottomMargin = (height - yPos) + radius + mShapePadding;
                mGravity = LayoutAlignment.BOTTOM;
            } else {
                mContentTopMargin = yPos + radius + mShapePadding;
                mContentBottomMargin = 0;
                mGravity = LayoutAlignment.TOP;
            }
        }
    }


    private void applyLayoutParams() {

        if (contentHintView != null && contentHintView.getLayoutConfig() != null) {
            LayoutConfig contentLP = (LayoutConfig) contentHintView.getLayoutConfig();

            boolean layoutParamsChanged = false;

            if (contentLP.getMarginBottom() != mContentBottomMargin) {
                contentLP.setMarginBottom(mContentBottomMargin);
                layoutParamsChanged = true;
            }

            if (contentLP.getMarginTop() != mContentTopMargin) {
                contentLP.setMarginTop(mContentTopMargin);
                layoutParamsChanged = true;
            }

            if (contentLP.alignment != mGravity) {
                contentLP.alignment = mGravity;
                layoutParamsChanged = true;
            }

            if (layoutParamsChanged) {
                contentHintView.setLayoutConfig(contentLP);
            }

            updateToolTip();
        }
    }

    private void updateToolTip() {
        if (toolTip != null) {

            if (!toolTipShown) {
                toolTipShown = true;
                toolTip.show(tooltipMargin, mGravity, mShapePadding);
            }
        }
    }

    void setPosition(Point point) {
        setPosition(point.getPointXToInt(), point.getPointYToInt());
    }

    public void setPosition(int x, int y) {
        mXPosition = x;
        mYPosition = y;
    }

    private void setTitleText(String contentText) {
        if (mTitleTextView != null && !contentText.equals("")) {
            mContentTextView.setTextColor(new Color(Color.getIntColor("#a6b2c2")));
            mTitleTextView.setText(contentText);
        }
    }

    private void setContentText(String contentText) {
        if (mContentTextView != null) {
            mContentTextView.setText(contentText);
        }
    }

    private void setToolTip(ShowcaseTooltip toolTip) {
        this.toolTip = toolTip;
    }

    private void setDismissText(String dismissText) {
        if (mDismissButton != null) {
            mDismissButton.setText(dismissText);
            updateDismissButton();
        }
    }

    private void setSkipText(String skipText) {
        if (mSkipButton != null) {
            mSkipButton.setText(skipText);
            updateSkipButton();
        }
    }

    private void setTitleTextColor(Color textColour) {
        if (mTitleTextView != null) {
            mTitleTextView.setTextColor(textColour);
        }
    }

    private void setContentTextColor(Color textColour) {
        if (mContentTextView != null) {
            mContentTextView.setTextColor(textColour);
        }
    }

    private void setDismissTextColor(Color textColour) {
        if (mDismissButton != null) {
            mDismissButton.setTextColor(textColour);
        }
    }

    private void setShapePadding(int padding) {
        mShapePadding = padding;
    }

    private void setTooltipMargin(int margin) {
        tooltipMargin = margin;
    }

    private void setDismissOnTouch(boolean dismissOnTouch) {
        mDismissOnTouch = dismissOnTouch;
    }

    private void setMaskColour(int maskColour) {
        mMaskColour = maskColour;
    }

    private void setDelay(long delayInMillis) {
        mDelayInMillis = delayInMillis;
    }

    private void setFadeDuration(long fadeDurationInMillis) {
        mFadeDurationInMillis = fadeDurationInMillis;
    }

    private void setTargetTouchable(boolean targetTouchable) {
        mTargetTouchable = targetTouchable;
    }

    private void setDismissOnTargetTouch(boolean dismissOnTargetTouch) {
        mDismissOnTargetTouch = dismissOnTargetTouch;
    }

    private void setUseFadeAnimation(boolean useFadeAnimation) {
        mUseFadeAnimation = useFadeAnimation;
    }

    public void addShowcaseListener(IShowcaseListener showcaseListener) {
        if (mListeners != null)
            mListeners.add(showcaseListener);
    }

    public void removeShowcaseListener(MaterialShowcaseSequence showcaseListener) {

        if ((mListeners != null) && mListeners.contains(showcaseListener)) {
            mListeners.remove(showcaseListener);
        }
    }

    void setDetachedListener(IDetachedListener detachedListener) {
        mDetachedListener = detachedListener;
    }

    public void setShape(Shape mShape) {
        this.mShape = mShape;
    }

    public void setAnimationFactory(IAnimationFactory animationFactory) {
        this.mAnimationFactory = animationFactory;
    }

    /**
     * 设置配置参数
     *
     * @param config 配置
     */
    public void setConfig(ShowcaseConfig config) {

        if (config.getDelay() > -1) {
            setDelay(config.getDelay());
        }

        if (config.getFadeDuration() > 0) {
            setFadeDuration(config.getFadeDuration());
        }

        if (config.getContentTextColor() > 0) {
            setContentTextColor(new Color(config.getContentTextColor()));
        }

        if (config.getDismissTextColor() > 0) {
            setDismissTextColor(new Color(config.getDismissTextColor()));
        }

        if (config.getMaskColor() > 0) {
            setMaskColour(config.getMaskColor());
        }

        if (config.getShape() != null) {
            setShape(config.getShape());
        }

        if (config.getShapePadding() > -1) {
            setShapePadding(config.getShapePadding());
        }

        if (config.getRenderOverNavigationBar() != null) {
            setRenderOverNavigationBar(config.getRenderOverNavigationBar());
        }
    }

    /**
     * 更新关闭按钮
     */
    private void updateDismissButton() {
        if (mDismissButton != null) {
            if (TextTool.isNullOrEmpty(mDismissButton.getText())) {
                mDismissButton.setVisibility(HIDE);
            } else {
                mDismissButton.setVisibility(VISIBLE);
            }
        }
    }

    void updateSkipButton() {
        if (mSkipButton != null) {
            if (TextTool.isNullOrEmpty(mSkipButton.getText())) {
                mSkipButton.setVisibility(HIDE);
            } else {
                mSkipButton.setVisibility(VISIBLE);
            }
        }
    }

    private void singleUse(String showcaseID) {
        mSingleUse = true;
        PrefsManager.getInstance().setShowcaseID(showcaseID);
    }

    public void removeFromWindow() {
        if (getComponentParent() != null) {
            if(getComponentParent() instanceof ComponentContainer){
                getComponentParent().removeComponent(this);
            }
        }
        removeAllComponents();
        mAnimationFactory = null;
        mHandler = null;
    }

    private ComponentContainer getRoot(Component component) {
        ComponentParent componentParent = component.getComponentParent();
        while (componentParent != null) {
            if (componentParent.getComponentParent() == null) {
                break;
            }
            componentParent = componentParent.getComponentParent();
        }
        return (ComponentContainer) componentParent;
    }

    /**
     * 显示
     */
    public void show() {
        if (mSingleUse) {
            if (PrefsManager.getInstance().isFinished()) {
                mWasDismissed = true;
                return;
            } else {
                PrefsManager.getInstance().setFinished();
            }
        }

        ViewTarget viewTarget = (ViewTarget) mTarget;
        if (viewTarget.getView() == null) {
            return;
        }

        ComponentContainer.LayoutConfig layoutConfig = new ComponentContainer.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_PARENT);
        getRoot(viewTarget.getView()).addComponent(this, layoutConfig);

        if (toolTip != null) {
            toolTip.configureTarget(this, viewTarget.getView());
        }

        mHandler = new EventHandler(EventRunner.getMainEventRunner());
        mHandler.postTask(() -> {
            addChildComponent();
            if (mShouldAnimate && isBoundToWindow()) {
                fadeIn();
            } else {
                setVisibility(VISIBLE);
                notifyOnDisplayed();
            }
        }, mDelayInMillis + 50);

    }

    /**
     * 添加背景、内容布局
     */
    private void addChildComponent() {
        BackgroundView backgroundView = new BackgroundView(mContext);
        backgroundView.setData(mShape, mMaskColour, mXPosition, mYPosition);
        addComponent(backgroundView);

        addComponent(contentHintView);
        updateDismissButton();
        applyLayoutParams();
    }


    public void hide() {
        mWasDismissed = true;
        if (mShouldAnimate) {
            animateOut();
        } else {
            removeFromWindow();
        }
    }

    public void skip() {
        mWasSkipped = true;
        if (mShouldAnimate) {
            animateOut();
        } else {
            removeFromWindow();
        }
    }

    public void fadeIn() {
        setVisibility(INVISIBLE);
        mAnimationFactory.animateInView(this, mTarget.getPoint(), mFadeDurationInMillis,
                new IAnimationFactory.AnimationStartListener() {
                    @Override
                    public void onAnimationStart() {
                        setVisibility(Component.VISIBLE);
                        notifyOnDisplayed();
                    }
                }
        );
    }

    public void animateOut() {
        mAnimationFactory.animateOutView(this, mTarget.getPoint(), mFadeDurationInMillis, new IAnimationFactory.AnimationEndListener() {
            @Override
            public void onAnimationEnd() {
                setVisibility(INVISIBLE);
                removeFromWindow();
            }
        });
    }

    /**
     * 根据id，重置对应的状态
     *
     * @param showcaseID 标志id
     */
    public static void resetSingleUse(String showcaseID) {
        PrefsManager.getInstance().setShowcaseID(showcaseID);
        PrefsManager.getInstance().resetShowcase();
    }

    /**
     * 清空全部状态数据
     */
    public static void resetAll() {
        PrefsManager.getInstance().resetAll();
    }

    private void setRenderOverNavigationBar(boolean mRenderOverNav) {
        this.mRenderOverNav = mRenderOverNav;
    }


    public static class Builder {
        private static final int CIRCLE_SHAPE = 0;
        private static final int RECTANGLE_SHAPE = 1;
        private static final int NO_SHAPE = 2;
        private static final int OVAL_SHAPE = 3;

        private boolean fullWidth = false;
        private int shapeType = CIRCLE_SHAPE;
        private final MaterialShowcaseView showcaseView;
        private final Ability ability;

        public Builder(Ability ability) {
            this.ability = ability;
            showcaseView = new MaterialShowcaseView(ability);
            showcaseView.setAbility(ability);
        }

        public Builder setGravity(int gravity) {
            showcaseView.setGravity(gravity);
            return this;
        }

        public Builder setTarget(Component target) {
            showcaseView.setTarget(new ViewTarget(target));
            return this;
        }

        public Builder setDismissText(int resId) {
            return setDismissText(ability.getString(resId));
        }

        public Builder setDismissText(String dismissText) {
            showcaseView.setDismissText(dismissText);
            return this;
        }

        public Builder setSkipText(int resId) {
            return setSkipText(ability.getString(resId));
        }

        public Builder setSkipText(String skipText) {
            showcaseView.setSkipText(skipText);
            return this;
        }

        public Builder setContentText(int resId) {
            return setContentText(ability.getString(resId));
        }

        public Builder setContentText(String text) {
            showcaseView.setContentText(text);
            return this;
        }

        public Builder setTitleText(int resId) {
            return setTitleText(ability.getString(resId));
        }

        public Builder setTitleText(String text) {
            showcaseView.setTitleText(text);
            return this;
        }

        public Builder setToolTip(ShowcaseTooltip toolTip) {
            showcaseView.setToolTip(toolTip);
            return this;
        }

        public Builder setTargetTouchable(boolean targetTouchable) {
            showcaseView.setTargetTouchable(targetTouchable);
            return this;
        }

        public Builder setDismissOnTargetTouch(boolean dismissOnTargetTouch) {
            showcaseView.setDismissOnTargetTouch(dismissOnTargetTouch);
            return this;
        }

        public Builder setDismissOnTouch(boolean dismissOnTouch) {
            showcaseView.setDismissOnTouch(dismissOnTouch);
            return this;
        }

        public Builder setMaskColour(int maskColour) {
            showcaseView.setMaskColour(maskColour);
            return this;
        }

        public Builder setTitleTextColor(Color textColour) {
            showcaseView.setTitleTextColor(textColour);
            return this;
        }

        public Builder setContentTextColor(Color textColour) {
            showcaseView.setContentTextColor(textColour);
            return this;
        }

        public Builder setDismissTextColor(Color textColour) {
            showcaseView.setDismissTextColor(textColour);
            return this;
        }

        public Builder setDelay(int delayInMillis) {
            showcaseView.setDelay(delayInMillis);
            return this;
        }

        public Builder setFadeDuration(int fadeDurationInMillis) {
            showcaseView.setFadeDuration(fadeDurationInMillis);
            return this;
        }

        public Builder setListener(IShowcaseListener listener) {
            showcaseView.addShowcaseListener(listener);
            return this;
        }

        public Builder singleUse(String showcaseID) {
            showcaseView.singleUse(showcaseID);
            return this;
        }

        public Builder setShape(Shape shape) {
            showcaseView.setShape(shape);
            return this;
        }

        public Builder withCircleShape() {
            shapeType = CIRCLE_SHAPE;
            return this;
        }

        public Builder withOvalShape() {
            shapeType = OVAL_SHAPE;
            return this;
        }

        public Builder withoutShape() {
            shapeType = NO_SHAPE;
            return this;
        }

        public Builder setShapePadding(int padding) {
            showcaseView.setShapePadding(padding);
            return this;
        }

        public Builder setTooltipMargin(int margin) {
            showcaseView.setTooltipMargin(margin);
            return this;
        }

        public Builder withRectangleShape() {
            return withRectangleShape(false);
        }

        public Builder withRectangleShape(boolean fullWidth) {
            this.shapeType = RECTANGLE_SHAPE;
            this.fullWidth = fullWidth;
            return this;
        }

        public Builder renderOverNavigationBar() {
            showcaseView.setRenderOverNavigationBar(true);
            return this;
        }

        public Builder useFadeAnimation() {
            showcaseView.setUseFadeAnimation(true);
            return this;
        }

        public MaterialShowcaseView build() {
            if (showcaseView.mShape == null) {
                switch (shapeType) {
                    case RECTANGLE_SHAPE: {
                        showcaseView.setShape(new RectangleShape(showcaseView.mTarget.getBounds(), fullWidth));
                        break;
                    }
                    default:
                    case CIRCLE_SHAPE: {
                        showcaseView.setShape(new CircleShape(showcaseView.mTarget));
                        break;
                    }
                    case NO_SHAPE: {
                        showcaseView.setShape(new NoShape());
                        break;
                    }
                    case OVAL_SHAPE: {
                        showcaseView.setShape(new OvalShape(showcaseView.mTarget));
                        break;
                    }
                }
            }

            if (showcaseView.mAnimationFactory == null) {
                if (showcaseView.mUseFadeAnimation) {
                    showcaseView.setAnimationFactory(new FadeAnimationFactory());
                } else {
                    showcaseView.setAnimationFactory(new CircularRevealAnimationFactory());
                }
            }

            showcaseView.mShape.setPadding(showcaseView.mShapePadding);

            return showcaseView;
        }

        public void show() {
            build().show();
        }
    }

    public static class BackgroundView extends Component implements DrawTask {
        private final Paint mEraser;
        private Shape mShape;
        private int mXPosition;
        private int mYPosition;
        private int mMaskColour;

        public BackgroundView(Context context) {
            this(context, null);
        }

        public BackgroundView(Context context, AttrSet attrSet) {
            this(context, attrSet, null);
        }

        public BackgroundView(Context context, AttrSet attrSet, String styleName) {
            super(context, attrSet, styleName);
            mEraser = new Paint();
            mEraser.setAntiAlias(true);
        }

        public void setData(Shape shape, int color, int xPosition, int yPosition) {
            mShape = shape;
            mMaskColour = color;
            mXPosition = xPosition;
            mYPosition = yPosition;
            addDrawTask(this);
        }

        @Override
        public void onDraw(Component component, Canvas canvas) {
            //创建矩形
            RectFloat rectFloat = new RectFloat(0f, 0f, getWidth(), getHeight());
            //保存当前图层并创建指定大小的图层，之后的操作都在新图层上
            int layerId = canvas.saveLayer(rectFloat, mEraser);
            //目标颜色，形状
            mShape.draw(canvas, mEraser, mXPosition, mYPosition);
            //源颜色，叠加模式：在源颜色和目标颜色的非重叠区域绘制源颜色。
            canvas.drawColor(mMaskColour, Canvas.PorterDuffMode.SRC_OUT);
            //恢复到原图层
            canvas.restoreToCount(layerId);
        }
    }

}
